package com.sunshine.security;

import com.sunshine.exception.InvalidJwtTokenException;
import com.sunshine.util.AppProps;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.time.Instant;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import static io.jsonwebtoken.SignatureAlgorithm.HS256;
import static java.lang.System.currentTimeMillis;
import static java.util.UUID.randomUUID;

/**
 * Created by along on 4/19/18.
 */
@Component
public class JsonWebToken {

    public static final String TOKEN_HEADER = "Authorization";
    public static final String TOKEN_HEAD = "Bearer ";

    private static final String USERNAME_CLAIM = "sub";

    private static final String CREATED_CLAIM = "iat";

    private static final String JTI_CLAIM = "jti";

    private static String SECRET = AppProps.get("jwt.secret").orElse("");

    private static long EXPIRATION = AppProps.get("jwt.expiration").map(Long::parseLong).orElse(0L);

    private JsonWebToken() {
    }

    public static Optional<String> getSubjectFromToken(String token)
            throws InvalidJwtTokenException {
        return getClaimsFromToken(token)
                .map(Claims::getSubject);
    }

    public static Optional<Instant> getCreatedAtFromToken(String token)
            throws InvalidJwtTokenException {
        return getClaimsFromToken(token)
                .map(value -> value.get(CREATED_CLAIM))
                .map(stamp -> Instant.ofEpochSecond((Long) stamp));
    }

    public static Optional<Instant> getExpirationFromToken(String token)
            throws InvalidJwtTokenException {
        return getClaimsFromToken(token)
                .map(Claims::getExpiration)
                .map(date -> Instant.ofEpochSecond(date.getTime()));
    }

    public static Optional<Claims> getClaimsFromToken(String token)
            throws InvalidJwtTokenException {
        try {
            return Optional.ofNullable(
                    Jwts.parser().setSigningKey(SECRET)
                            .parseClaimsJws(token)
                            .getBody());
        } catch (MalformedJwtException e) {
            throw new InvalidJwtTokenException(e.getMessage());
        }
    }

    private static Date generateExpirationDate() {
        return new Date(currentTimeMillis() + EXPIRATION);
    }

    public static String generateToken(UserDetails userDetails) {
        if (userDetails == null) {
            throw new NullPointerException("userDetails is null");
        }

        if (userDetails.getUsername() == null) {
            throw new NullPointerException("userDetails.getUsername() is null");
        }

        return generateToken(userDetails.getUsername());
    }

    public static String generateToken(int id) {
        return generateToken((Object) id);
    }

    private static String generateToken(Object subject) {
        Map<String, Object> claims = new HashMap<>();
        claims.put(USERNAME_CLAIM, subject);
        claims.put(CREATED_CLAIM, currentTimeMillis() / 1000);
        claims.put(JTI_CLAIM, randomUUID());
        return generateTokenFromClaims(claims);
    }

    private static String generateTokenFromClaims(Map<String, Object> claims) {
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(generateExpirationDate())
                .signWith(HS256, SECRET)
                .compact();
    }

    private static Optional<String> getTokenFromRequest(HttpServletRequest request) {
        if (request == null) {
            throw new NullPointerException("request is null");
        }
        return Optional.ofNullable(request.getHeader(TOKEN_HEADER))
                .filter(token -> token.startsWith(TOKEN_HEAD))
                .map(token -> token.substring(TOKEN_HEAD.length()));

    }

    public static Optional<String> getSubjectFromRequest(HttpServletRequest request)
            throws InvalidJwtTokenException {
        Optional<String> token = getTokenFromRequest(request);
        if (token.isPresent()) {
            return getSubjectFromToken(token.get());
        }
        return Optional.empty();
    }
}
